package Main;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Label;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.Image;

import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.filechooser.FileNameExtensionFilter;

import java.awt.event.ActionListener;
import java.io.File;

/**
 * This class is the main GUI class for the ImageManipulation program. Defaults
 * should be automatically initiated, simply instantiate the class.<br>
 * Note: this class should not be instantiated more than once.
 * 
 * @author jboyd
 * 
 */
public class GUI {

	private static Frame frame;
	private MenuBar menubar = new MenuBar();
	private Menu fileMenu;
	private Menu imageMenu;
	private MenuItem resize;
	private MenuItem open;
	private static MenuItem save;
	private static MenuItem close;
	private MenuItem exit;
	private Label infoArea = new Label();
	static BufferedImage image;
	private static File f;
	private static String curImageName = "";
	private static ImageCanvas imageCanvas;
	private static Panel imagePanel = new Panel();
	private static Graphics g;
	leftBar leftbar = new leftBar();

	GUI() {
		setLandF();
		frame = new Frame();
		initDefaultMenu();
		initFrameDefaults();
		// initate an infinite thread
		// add an extension of Panel to the Frame in Borderlayout.center
		// this extended object will paint image if it is not null
	}

	private void initDefaultMenu() {
		// Create menu items: File (Open, Save, |, Exit)
		imageCanvas = new ImageCanvas();
		fileMenu = new Menu("File");
		fileMenu.setFont(new Font("SERIF", Font.BOLD, 15));
		open = new MenuItem("Open");
		open.addActionListener(new OpenListener());
		save = new MenuItem("Save");
		save.setEnabled(false);
		save.addActionListener(new SaveListener());
		close = new MenuItem("Close");
		close.addActionListener(new CloseListener());
		close.setEnabled(false);
		exit = new MenuItem("Exit");
		exit.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (image != null) {
					askToQuit();
				} else {
					System.exit(0);
				}
			}
		});
		fileMenu.add(open);
		fileMenu.add(save);
		fileMenu.add(close);
		fileMenu.addSeparator();
		fileMenu.add(exit);

		imageMenu = new Menu("Image");
		imageMenu.setFont(new Font("SERIF", Font.BOLD, 15));

		resize = new MenuItem("Resize");
		resize.addActionListener(new ResizeListener());

		imageMenu.add(resize);

		menubar.add(fileMenu);
		menubar.add(imageMenu);
	}

	private void initFrameDefaults() {
		frame.setSize(new Dimension((int) (java.awt.Toolkit.getDefaultToolkit()
				.getScreenSize().width * 0.90), (int) (java.awt.Toolkit
				.getDefaultToolkit().getScreenSize().height * 0.90)));
		frame.addWindowListener(new FrameListener());
		frame.setResizable(false);
		frame.setLayout(new BorderLayout());
		frame.setMenuBar(menubar);
		imagePanel.setSize((int) (frame.getWidth() * 0.75),
				(int) (frame.getHeight() * 0.75));
		imagePanel.add(imageCanvas);
		infoArea.setFont(new Font("SERIF", Font.BOLD, 15));
		imagePanel.addMouseMotionListener(new ImagePanelMouseMotionListener());
		frame.add(imagePanel, BorderLayout.CENTER);
		frame.add(infoArea, BorderLayout.SOUTH);
		frame.add(leftbar, BorderLayout.WEST);
		frame.setVisible(true);
		infoArea.setVisible(true);
	}

	private void askToQuit() {
		if (JOptionPane.showConfirmDialog(frame,
				"Are you sure you want to quit?", "Quit?",
				JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
			System.exit(0);
		}
	}

	private void setLandF() {
		try {
			// Set System L&F
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (UnsupportedLookAndFeelException e) {
			try {
				UIManager.setLookAndFeel(UIManager
						.getCrossPlatformLookAndFeelClassName());
			} catch (Exception ex) {
			}
		} catch (ClassNotFoundException e) {
			try {
				UIManager.setLookAndFeel(UIManager
						.getCrossPlatformLookAndFeelClassName());
			} catch (Exception ex) {
			}
		} catch (InstantiationException e) {
			try {
				UIManager.setLookAndFeel(UIManager
						.getCrossPlatformLookAndFeelClassName());
			} catch (Exception ex) {
			}
		} catch (IllegalAccessException e) {
			try {
				UIManager.setLookAndFeel(UIManager
						.getCrossPlatformLookAndFeelClassName());
			} catch (Exception ex) {
			}
		}
	}

	/**
	 * Method to add panel to the west side of the Frame's borderlayout.
	 * 
	 * @author jboyd
	 * 
	 */
	public void addOptionsPanel(Panel p) {
		frame.add(p, BorderLayout.WEST);
		frame.repaint();
	}

	/**
	 * Class shamelessly stolen from my previous work on the first problem.
	 * Returns the current image as a 2d array of colors[y][x].
	 * 
	 * @return Color[][] - in [y][x] format. Returns the current image as a 2D
	 *         color array.
	 */
	public static Color[][] getImageColors() {
		if (checkIfImageLoaded()) {
			Color[][] colorArray = new Color[image.getHeight()][image
					.getWidth()];
			for (int y = 0; y < colorArray.length; y++) {
				for (int x = 0; x < colorArray[y].length; x++) {
					colorArray[y][x] = new Color(image.getRGB(x, y));
				}
			}
			return colorArray;
		} else {
			return null;
		}
	}

	public static BufferedImage getImageBuffered() {
		checkIfImageLoaded();
		return image;
	}

	public static Color[][] getImageColors(BufferedImage b) {
		Color[][] colorArray = new Color[b.getHeight()][b.getWidth()];
		for (int y = 0; y < colorArray.length; y++) {
			for (int x = 0; x < colorArray[y].length; x++) {
				colorArray[y][x] = new Color(b.getRGB(x, y));
			}
		}
		return colorArray;
	}

	public Color getImageColors(int x, int y) {
		if (checkIfImageLoaded()) {
			return getImageColors()[y][x];
		} else {
			return Color.white;
		}
	}

	public static void setImageColors(Color[][] c) {
		if (checkIfImageLoaded()) {
			for (int y = 0; y < c.length; y++) {
				for (int x = 0; x < c[0].length; x++) {
					if (c[y][x] != null && y <= image.getHeight()
							& x <= image.getWidth()) {
						image.setRGB(x, y, c[y][x].getRGB());
					}
				}
			}
			g = imagePanel.getGraphics();
			imageCanvas.paint(g);
		} else
			System.out.println("Image not loaded");
	}

	public static void setImageColors(BufferedImage b) {
		Color[][] temp = getImageColors(b);
		setImageColors(temp);
	}

	public static void openImageFile() {
		JFileChooser choice;
		FileNameExtensionFilter imageFilter = new FileNameExtensionFilter(
				"PNG, JPG, GIF", "gif", "jpg", "png");
		try {
			choice = new JFileChooser();
			choice.setFileFilter(imageFilter);
			choice.setMultiSelectionEnabled(false);
			if (!(choice.showOpenDialog(frame) == JFileChooser.CANCEL_OPTION)) {
				f = choice.getSelectedFile();
				if (f.isFile()) {
					image = ImageIO.read(f);
					curImageName = f.getAbsolutePath();
					g = imagePanel.getGraphics();
					imageCanvas.paint(g);
				}
			}
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(frame,
					"Invalid image choice or file selected is not an image.",
					"Error", JOptionPane.INFORMATION_MESSAGE);
		}
		if (image != null) {
			save.setEnabled(true);
			close.setEnabled(true);
		}
	}

	private static Boolean checkIfImageLoaded() {
		if (image == null) {
			JOptionPane.showMessageDialog(frame, "No image loaded!",
					"No image has been loaded yet!", JOptionPane.ERROR_MESSAGE);
		}
		return (true);
	}

	/**
	 * Method used to alter the image by classes/methods that work based on
	 * current coordinates of the cursor. Since the image drawn doesn't
	 * necessarily correspond to the size of the internal image, this method
	 * will translate a given cursor coordinate to the corresponding coordinate
	 * on the image. For non-cursor based positioning, <i>setImageColors</i>
	 * should be used instead.
	 * 
	 * @param c
	 *            Color[][] where each pixel corresponds to a cursor position.
	 *            This array will be translated into the appropriate pixel on
	 *            the image based ont he current scaling.
	 */
	public static void setImageColorsBasedOnPanelCoords(Color[][] c) {
		for (int y = 0; y < c.length; y++) {
			for (int x = 0; x < c[y].length; x++) {
				if (c[y][x] != null
						&& calcShownToRealRatio() * y <= image.getHeight()
						& calcShownToRealRatio() * x <= image.getWidth()) {
					image.setRGB((int) (calcShownToRealRatio() * x),
							(int) (calcShownToRealRatio() * y),
							c[y][x].getRGB());
				}
			}
		}
	}

	public void setWhiteBackground() {
		imageCanvas.setWhiteBackground();
		imageCanvas.repaint();
	}

	public void setImageColorsBasedOnPanelCoords(BufferedImage b) {
		Color[][] temp = getImageColors(b);
		setImageColorsBasedOnPanelCoords(temp);
	}

	private Dimension scaleImage(Dimension item, Dimension scaleTo, float ratio) {
		Dimension result = new Dimension(item);
		// reimplement scaling as a function on two dimensions - scaleTo is the
		// dimension of the imagePanel, new is the adjusted size to fit within
		// the panel
		if (result.width > scaleTo.width) {
			result.height -= (result.width - scaleTo.width) / ratio;
			result.width = scaleTo.width;
		}
		if (result.height > scaleTo.height) {
			result.width -= (result.height - scaleTo.height) * ratio;
			result.height = scaleTo.height;
		}
		return result;
	}

	private float calcRatio(Dimension d) {
		return (float) d.width / (float) d.height;
	}

	private static float calcShownToRealRatio() {
		float result = (float) 1.0;
		if (image != null && image.getWidth() > imagePanel.getWidth()) {
			result = Math.max(
					(float) image.getWidth() / (float) imagePanel.getWidth(),
					(float) image.getHeight() / (float) imagePanel.getHeight());
		}
		return result;
	}

	private class ImageCanvas extends Canvas {
		private static final long serialVersionUID = 1L;

		public ImageCanvas() {
		}

		@Override
		public void paint(Graphics g) {
			Dimension imageD;
			Dimension imageP = new Dimension(imagePanel.getWidth(),
					imagePanel.getHeight());
			setWhiteBackground();
			if (image != null) {
				imageD = new Dimension(image.getWidth(), image.getHeight());
				if (imageD.width > imageP.width | imageD.height > imageP.height) {
					imageD = scaleImage(imageD, imageP, calcRatio(imageD));
				}
				g.drawImage(image, 0, 0, imageD.width, imageD.height, null);
			} else
				System.out.println("null image");
			g.dispose();
		}

		public void setWhiteBackground() {
			g.setColor(Color.WHITE);
			g.fillRect(0, 0, imagePanel.getWidth(), imagePanel.getHeight());
		}
	}

	private class FrameListener extends WindowAdapter {
		public void windowClosing(WindowEvent e) {
			if (image != null) {
				askToQuit();
			} else {
				System.exit(0);
			}
		}
	}

	private class OpenListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			// open a jfilechooser to select a file, try to read that in as the
			// contents of the buffered image
			// if it fails, throw a message up to the user
			// jfilechooser should be limited to the available image reading
			// options
			/*
			 * JFileChooser choice; FileNameExtensionFilter imageFilter = new
			 * FileNameExtensionFilter("PNG, JPG, GIF", "gif", "jpg", "png");
			 * try{ choice = new JFileChooser();
			 * choice.setFileFilter(imageFilter);
			 * choice.setMultiSelectionEnabled(false);
			 * if(!(choice.showOpenDialog(frame) ==
			 * JFileChooser.CANCEL_OPTION)){ f = choice.getSelectedFile();
			 * if(f.isFile()){ image = ImageIO.read(f); curImageName =
			 * f.getAbsolutePath(); g = imagePanel.getGraphics();
			 * imageCanvas.paint(g); } } } catch(Exception
			 * ex){JOptionPane.showMessageDialog(frame,
			 * "Invalid image choice or file selected is not an image.",
			 * "Error", JOptionPane.INFORMATION_MESSAGE); } if(image !=
			 * null){save.setEnabled(true);close.setEnabled(true);} }
			 */
			openImageFile();
		}
	}

	private class SaveListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			JFileChooser choice;
			FileNameExtensionFilter imageFilter = new FileNameExtensionFilter(
					"PNG, JPG, GIF", "gif", "jpg", "png");
			try {
				choice = new JFileChooser();
				choice.setFileFilter(imageFilter);
				choice.setMultiSelectionEnabled(false);
				if (!(choice.showSaveDialog(frame) == JFileChooser.CANCEL_OPTION)) {
					String fileName = choice.getSelectedFile()
							.getAbsolutePath();
					String extension = "";
					if (fileName.contains(".png")) {
						extension = "png";
					} else if (fileName.contains(".jpg")) {
						extension = "jpg";
					} else if (fileName.contains(".gif")) {
						extension = "gif";
					} else {
						extension = curImageName.split("\\.")[curImageName
								.split("\\.").length - 1];
						fileName += "." + extension;
					}
					ImageIO.write(image, extension, new File(fileName));
				}
			} catch (Exception ex) {
				ex.printStackTrace();
				JOptionPane.showMessageDialog(frame,
						"Unable to save image file.", "Error",
						JOptionPane.INFORMATION_MESSAGE);
			}
		}
	}

	private class CloseListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent a) {
			image = null;
			save.setEnabled(false);
			g = imagePanel.getGraphics();
			imageCanvas.paint(g);
		}

	}

	private class ResizeListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {
	 		String factorString = JOptionPane.showInputDialog("Resize %");
			double factor = (Double.parseDouble(factorString) / 100);
			int scaleX = (int) (image.getWidth() * factor);
			int scaleY = (int) (image.getHeight() * factor);
			Image redrawnImage = image.getScaledInstance(scaleX, scaleY, Image.SCALE_SMOOTH);
			BufferedImage temp = new BufferedImage(scaleX, scaleY, 1);
			temp.getGraphics().drawImage(redrawnImage, 0, 0, null);
			image = temp;
			g = imagePanel.getGraphics();
			imageCanvas.paint(g);
		}
	}

	private class ImagePanelMouseMotionListener implements MouseMotionListener {
		@Override
		public void mouseDragged(MouseEvent me) {
		}

		@Override
		public void mouseMoved(MouseEvent me) {
			if (image != null) {
				if (!((int) (calcShownToRealRatio() * me.getX()) > image
						.getWidth())
						&& !((int) (calcShownToRealRatio() * me.getY()) > image
								.getHeight())) {
					Color temp = getImageColors(
							(int) (calcShownToRealRatio() * me.getX()),
							(int) (calcShownToRealRatio() * me.getY()));
					infoArea.setText("X: "
							+ (int) (calcShownToRealRatio() * me.getX())
							+ " Y: "
							+ (int) (calcShownToRealRatio() * me.getY())
							+ " R: " + temp.getRed() + " G: " + temp.getGreen()
							+ " B: " + temp.getBlue());
				} else {
					infoArea.setText("");
				}
			}
		}

	}
}
